● Zion Boggan
repos/Stepping App/apps/web/src/app/city/[slug]/floor/page.tsx
zionboggan.com ↗
478 lines · tsx
History for this file →
1
import Link from "next/link";
2
import { SiteFooter } from "@/components/site-footer";
3
import { notFound } from "next/navigation";
4
import { revalidatePath } from "next/cache";
5
import { serverCaller } from "@/lib/trpc/server";
6
 
7
export const dynamic = "force-dynamic";
8
 
9
export default async function FloorPage({
10
  params,
11
}: {
12
  params: Promise<{ slug: string }>;
13
}) {
14
  const { slug } = await params;
15
  const caller = serverCaller();
16
  const city = await caller.city.bySlug({ slug });
17
  if (!city) notFound();
18
 
19
  const cityId = city.id;
20
  const [steppingGroups, djs, promoters, instructors, classes] = await Promise.all([
21
    caller.steppingGroup.list({ cityId }),
22
    caller.dj.list({ cityId }),
23
    caller.promoter.list({ cityId }),
24
    caller.instructor.list({ cityId }),
25
    caller.class.list({ cityId }),
26
  ]);
27
 
28
  async function addSteppingGroup(formData: FormData) {
29
    "use server";
30
    const name = String(formData.get("name") ?? "").trim();
31
    const bio = String(formData.get("bio") ?? "").trim();
32
    if (!name) return;
33
    await serverCaller().steppingGroup.create({
34
      cityId: city!.id,
35
      name,
36
      bio: bio || undefined,
37
    });
38
    revalidatePath(`/city/${slug}/floor`);
39
  }
40
 
41
  async function addDj(formData: FormData) {
42
    "use server";
43
    const name = String(formData.get("name") ?? "").trim();
44
    const bio = String(formData.get("bio") ?? "").trim();
45
    if (!name) return;
46
    await serverCaller().dj.create({
47
      cityId: city!.id,
48
      name,
49
      bio: bio || undefined,
50
    });
51
    revalidatePath(`/city/${slug}/floor`);
52
  }
53
 
54
  async function addPromoter(formData: FormData) {
55
    "use server";
56
    const name = String(formData.get("name") ?? "").trim();
57
    const bio = String(formData.get("bio") ?? "").trim();
58
    if (!name) return;
59
    await serverCaller().promoter.create({
60
      cityId: city!.id,
61
      name,
62
      bio: bio || undefined,
63
    });
64
    revalidatePath(`/city/${slug}/floor`);
65
  }
66
 
67
  async function addInstructor(formData: FormData) {
68
    "use server";
69
    const name = String(formData.get("name") ?? "").trim();
70
    const bio = String(formData.get("bio") ?? "").trim();
71
    if (!name) return;
72
    await serverCaller().instructor.create({
73
      cityId: city!.id,
74
      name,
75
      bio: bio || undefined,
76
    });
77
    revalidatePath(`/city/${slug}/floor`);
78
  }
79
 
80
  async function addClass(formData: FormData) {
81
    "use server";
82
    const title = String(formData.get("title") ?? "").trim();
83
    const instructor = String(formData.get("instructor") ?? "").trim();
84
    const schedule = String(formData.get("schedule") ?? "").trim();
85
    const isFree = formData.get("isFree") != null;
86
    if (!title) return;
87
    await serverCaller().class.create({
88
      cityId: city!.id,
89
      title,
90
      instructor: instructor || undefined,
91
      schedule: schedule || undefined,
92
      isFree,
93
    });
94
    revalidatePath(`/city/${slug}/floor`);
95
  }
96
 
97
  return (
98
    <main className="mx-auto w-full max-w-3xl px-5 py-16 sm:px-8">
99
      <Link href={`/city/${slug}`} className="kicker hover:text-gold">
100
        ← {city.name}
101
      </Link>
102
 
103
      <p className="kicker mt-10 text-ash">
104
        The Floor · {city.metroLabel}
105
      </p>
106
      <h1 className="display mt-3 text-7xl sm:text-8xl">The Floor</h1>
107
      <p className="serif mt-5 max-w-xl text-xl text-ash">
108
        The people who keep {city.name} stepping. Groups, DJs, promoters,
109
        instructors, and classes.
110
      </p>
111
 
112
      {}
113
      <section className="mt-16 border-t border-[var(--line)] pt-10">
114
        <div className="flex items-end justify-between">
115
          <div>
116
            <p className="kicker text-gold">Section 01</p>
117
            <h2 className="display mt-2 text-4xl">Stepping Groups</h2>
118
          </div>
119
          <span className="kicker text-ash">
120
            {String(steppingGroups.length).padStart(2, "0")} groups
121
          </span>
122
        </div>
123
 
124
        {steppingGroups.length === 0 ? (
125
          <p className="serif mt-8 text-xl text-ash">
126
            Stepping groups will be listed here as they come on board. Know
127
            one? Add it below.
128
          </p>
129
        ) : (
130
          <ul className="mt-6">
131
            {steppingGroups.map((g, i) => (
132
              <li
133
                key={g.id}
134
                className="flex items-center gap-5 border-b border-[var(--line)] py-6"
135
              >
136
                <span className="kicker w-8 text-gold">
137
                  {String(i + 1).padStart(2, "0")}
138
                </span>
139
                <div className="flex-1">
140
                  <Link
141
                    href={`/city/${slug}/profile/stepping-group/${g.id}`}
142
                    className="display text-2xl text-bone hover:text-gold sm:text-3xl"
143
                  >
144
                    {g.name}
145
                  </Link>
146
                  {g.bio ? (
147
                    <p className="serif mt-1 text-lg text-ash">{g.bio}</p>
148
                  ) : null}
149
                </div>
150
              </li>
151
            ))}
152
          </ul>
153
        )}
154
 
155
        <form action={addSteppingGroup} className="mt-8 flex flex-col gap-3">
156
          <input
157
            name="name"
158
            required
159
            placeholder="Group name (e.g. Smooth City Steppers)"
160
            className="serif border-b border-[var(--line)] bg-transparent py-3 text-2xl text-bone outline-none placeholder:text-ash focus:border-gold"
161
          />
162
          <input
163
            name="bio"
164
            placeholder="One line about the group (optional)"
165
            className="serif border-b border-[var(--line)] bg-transparent py-2 text-lg text-bone outline-none placeholder:text-ash focus:border-gold"
166
          />
167
          <div className="mt-2">
168
            <button
169
              type="submit"
170
              className="kicker bg-gold px-6 py-3 text-stage hover:bg-gold-bright"
171
            >
172
              Add group
173
            </button>
174
          </div>
175
        </form>
176
      </section>
177
 
178
      {}
179
      <section className="mt-16 border-t border-[var(--line)] pt-10">
180
        <div className="flex items-end justify-between">
181
          <div>
182
            <p className="kicker text-gold">Section 02</p>
183
            <h2 className="display mt-2 text-4xl">DJs</h2>
184
          </div>
185
          <span className="kicker text-ash">
186
            {String(djs.length).padStart(2, "0")} djs
187
          </span>
188
        </div>
189
 
190
        {djs.length === 0 ? (
191
          <p className="serif mt-8 text-xl text-ash">
192
            DJs will be listed here as they come on board. Add the first one
193
            below.
194
          </p>
195
        ) : (
196
          <ul className="mt-6">
197
            {djs.map((d, i) => (
198
              <li
199
                key={d.id}
200
                className="flex items-center gap-5 border-b border-[var(--line)] py-6"
201
              >
202
                <span className="kicker w-8 text-gold">
203
                  {String(i + 1).padStart(2, "0")}
204
                </span>
205
                <div className="flex-1">
206
                  <Link
207
                    href={`/city/${slug}/profile/dj/${d.id}`}
208
                    className="display text-2xl text-bone hover:text-gold sm:text-3xl"
209
                  >
210
                    {d.name}
211
                  </Link>
212
                  {d.bio ? (
213
                    <p className="serif mt-1 text-lg text-ash">{d.bio}</p>
214
                  ) : null}
215
                </div>
216
                <span className="kicker border border-[var(--line)] px-3 py-1">
217
                  DJ
218
                </span>
219
              </li>
220
            ))}
221
          </ul>
222
        )}
223
 
224
        <form action={addDj} className="mt-8 flex flex-col gap-3">
225
          <input
226
            name="name"
227
            required
228
            placeholder="DJ name (e.g. DJ Smooth)"
229
            className="serif border-b border-[var(--line)] bg-transparent py-3 text-2xl text-bone outline-none placeholder:text-ash focus:border-gold"
230
          />
231
          <input
232
            name="bio"
233
            placeholder="One line (optional)"
234
            className="serif border-b border-[var(--line)] bg-transparent py-2 text-lg text-bone outline-none placeholder:text-ash focus:border-gold"
235
          />
236
          <div className="mt-2">
237
            <button
238
              type="submit"
239
              className="kicker bg-gold px-6 py-3 text-stage hover:bg-gold-bright"
240
            >
241
              Add DJ
242
            </button>
243
          </div>
244
        </form>
245
      </section>
246
 
247
      {}
248
      <section className="mt-16 border-t border-[var(--line)] pt-10">
249
        <div className="flex items-end justify-between">
250
          <div>
251
            <p className="kicker text-gold">Section 03</p>
252
            <h2 className="display mt-2 text-4xl">Promoters</h2>
253
          </div>
254
          <span className="kicker text-ash">
255
            {String(promoters.length).padStart(2, "0")} promoters
256
          </span>
257
        </div>
258
 
259
        {promoters.length === 0 ? (
260
          <p className="serif mt-8 text-xl text-ash">
261
            Promoters will be listed here as they come on board. Add the one
262
            behind your next set below.
263
          </p>
264
        ) : (
265
          <ul className="mt-6">
266
            {promoters.map((p, i) => (
267
              <li
268
                key={p.id}
269
                className="flex items-center gap-5 border-b border-[var(--line)] py-6"
270
              >
271
                <span className="kicker w-8 text-gold">
272
                  {String(i + 1).padStart(2, "0")}
273
                </span>
274
                <div className="flex-1">
275
                  <Link
276
                    href={`/city/${slug}/profile/promoter/${p.id}`}
277
                    className="display text-2xl text-bone hover:text-gold sm:text-3xl"
278
                  >
279
                    {p.name}
280
                  </Link>
281
                  {p.bio ? (
282
                    <p className="serif mt-1 text-lg text-ash">{p.bio}</p>
283
                  ) : null}
284
                </div>
285
                <span className="kicker border border-[var(--line)] px-3 py-1">
286
                  PROMOTER
287
                </span>
288
              </li>
289
            ))}
290
          </ul>
291
        )}
292
 
293
        <form action={addPromoter} className="mt-8 flex flex-col gap-3">
294
          <input
295
            name="name"
296
            required
297
            placeholder="Promoter or brand (e.g. Westside Sets)"
298
            className="serif border-b border-[var(--line)] bg-transparent py-3 text-2xl text-bone outline-none placeholder:text-ash focus:border-gold"
299
          />
300
          <input
301
            name="bio"
302
            placeholder="One line (optional)"
303
            className="serif border-b border-[var(--line)] bg-transparent py-2 text-lg text-bone outline-none placeholder:text-ash focus:border-gold"
304
          />
305
          <div className="mt-2">
306
            <button
307
              type="submit"
308
              className="kicker bg-gold px-6 py-3 text-stage hover:bg-gold-bright"
309
            >
310
              Add promoter
311
            </button>
312
          </div>
313
        </form>
314
      </section>
315
 
316
      {}
317
      <section className="mt-16 border-t border-[var(--line)] pt-10">
318
        <div className="flex items-end justify-between">
319
          <div>
320
            <p className="kicker text-gold">Section 04</p>
321
            <h2 className="display mt-2 text-4xl">Instructors</h2>
322
          </div>
323
          <span className="kicker text-ash">
324
            {String(instructors.length).padStart(2, "0")} instructors
325
          </span>
326
        </div>
327
        <p className="serif mt-4 max-w-xl text-lg text-ash">
328
          Learn from the people who teach it. Instructors offering lessons in{" "}
329
          {city.name}.
330
        </p>
331
 
332
        {instructors.length === 0 ? (
333
          <p className="serif mt-8 text-xl text-ash">
334
            Instructors will be listed here as they come on board. Teach in{" "}
335
            {city.name}? Add your name below.
336
          </p>
337
        ) : (
338
          <ul className="mt-6">
339
            {instructors.map((t, i) => (
340
              <li
341
                key={t.id}
342
                className="flex items-center gap-5 border-b border-[var(--line)] py-6"
343
              >
344
                <span className="kicker w-8 text-gold">
345
                  {String(i + 1).padStart(2, "0")}
346
                </span>
347
                <div className="flex-1">
348
                  <Link
349
                    href={`/city/${slug}/profile/instructor/${t.id}`}
350
                    className="display text-2xl text-bone hover:text-gold sm:text-3xl"
351
                  >
352
                    {t.name}
353
                  </Link>
354
                  {t.bio ? (
355
                    <p className="serif mt-1 text-lg text-ash">{t.bio}</p>
356
                  ) : null}
357
                </div>
358
                <span className="kicker border border-[var(--line)] px-3 py-1">
359
                  LESSONS
360
                </span>
361
              </li>
362
            ))}
363
          </ul>
364
        )}
365
 
366
        <form action={addInstructor} className="mt-8 flex flex-col gap-3">
367
          <input
368
            name="name"
369
            required
370
            placeholder="Instructor name"
371
            className="serif border-b border-[var(--line)] bg-transparent py-3 text-2xl text-bone outline-none placeholder:text-ash focus:border-gold"
372
          />
373
          <input
374
            name="bio"
375
            placeholder="What you teach and how to book a lesson (optional)"
376
            className="serif border-b border-[var(--line)] bg-transparent py-2 text-lg text-bone outline-none placeholder:text-ash focus:border-gold"
377
          />
378
          <div className="mt-2">
379
            <button
380
              type="submit"
381
              className="kicker bg-gold px-6 py-3 text-stage hover:bg-gold-bright"
382
            >
383
              Add instructor
384
            </button>
385
          </div>
386
        </form>
387
      </section>
388
 
389
      {}
390
      <section className="mt-16 border-t border-[var(--line)] pt-10">
391
        <div className="flex items-end justify-between">
392
          <div>
393
            <p className="kicker text-gold">Section 05</p>
394
            <h2 className="display mt-2 text-4xl">Classes</h2>
395
          </div>
396
          <span className="kicker text-ash">
397
            {String(classes.length).padStart(2, "0")} classes
398
          </span>
399
        </div>
400
 
401
        {classes.length === 0 ? (
402
          <p className="serif mt-8 text-xl text-ash">
403
            Classes will be listed here as they come on board. Add the first
404
            one below.
405
          </p>
406
        ) : (
407
          <ul className="mt-6">
408
            {classes.map((c, i) => (
409
              <li
410
                key={c.id}
411
                className="flex items-center gap-5 border-b border-[var(--line)] py-6"
412
              >
413
                <span className="kicker w-8 text-gold">
414
                  {String(i + 1).padStart(2, "0")}
415
                </span>
416
                <div className="flex-1">
417
                  <Link
418
                    href={`/city/${slug}/profile/class/${c.id}`}
419
                    className="display text-2xl text-bone hover:text-gold sm:text-3xl"
420
                  >
421
                    {c.title}
422
                  </Link>
423
                  <p className="kicker mt-1 text-ash">
424
                    {[
425
                      c.instructor ? `WITH ${c.instructor.toUpperCase()}` : null,
426
                      c.schedule ? c.schedule.toUpperCase() : null,
427
                    ]
428
                      .filter(Boolean)
429
                      .join(" · ") || "SCHEDULE TBD"}
430
                  </p>
431
                </div>
432
                <span className="kicker border border-[var(--line)] px-3 py-1">
433
                  {c.isFree ? "FREE" : "CLASS"}
434
                </span>
435
              </li>
436
            ))}
437
          </ul>
438
        )}
439
 
440
        <form action={addClass} className="mt-8 flex flex-col gap-3">
441
          <input
442
            name="title"
443
            required
444
            placeholder="Class title (e.g. Intro to Chicago Stepping)"
445
            className="serif border-b border-[var(--line)] bg-transparent py-3 text-2xl text-bone outline-none placeholder:text-ash focus:border-gold"
446
          />
447
          <div className="mt-2 flex flex-wrap items-center gap-4">
448
            <input
449
              name="instructor"
450
              placeholder="Instructor"
451
              className="kicker border border-[var(--line)] bg-stage px-4 py-3 text-bone outline-none placeholder:text-ash focus:border-gold"
452
            />
453
            <input
454
              name="schedule"
455
              placeholder="Schedule (e.g. Tuesdays 7pm)"
456
              className="kicker border border-[var(--line)] bg-stage px-4 py-3 text-bone outline-none placeholder:text-ash focus:border-gold"
457
            />
458
            <label className="kicker flex items-center gap-2 text-ash">
459
              <input
460
                type="checkbox"
461
                name="isFree"
462
                className="h-4 w-4 accent-gold"
463
              />
464
              Free
465
            </label>
466
            <button
467
              type="submit"
468
              className="kicker bg-gold px-6 py-3 text-stage hover:bg-gold-bright"
469
            >
470
              Add class
471
            </button>
472
          </div>
473
        </form>
474
      </section>
475
      <SiteFooter />
476
    </main>
477
  );
478
}